﻿using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Phone.Controls;
using STEM123.StemService;

namespace STEM123
{
    /// <summary>
    /// Interacts with webservice to publish a presentation.
    /// </summary>
    public partial class PublishResults : PhoneApplicationPage
    {
        #region Private members

        /// <summary>
        /// Content information.
        /// </summary>
        private class ContentInfo
        {
            /// <summary>
            /// Index of slide.
            /// </summary>
            public int SlideIndex { get; set; }

            /// <summary>
            /// True if slide contains image else false.
            /// </summary>
            public bool IsImage { get; set; }
        }

        /// <summary>
        /// Proxy to webservice.
        /// </summary>
        private StemServiceClient client;

        /// <summary>
        /// Id of presentation.
        /// </summary>
        private uint presentationId;

        /// <summary>
        /// Reference to publish model.
        /// </summary>
        private IViewModel viewModel;

        /// <summary>
        /// List of pending actions.
        /// </summary>
        private Queue<Action> pendingActions;

        /// <summary>
        /// Total pending actions originally initiated.
        /// </summary>
        private int totalPendingActions;

        /// <summary>
        /// Occurs when connection with webservice is established.
        /// </summary>
        /// <param name="sender">Event related object.</param>
        /// <param name="e">Event specific arguments.</param>
        private void client_OpenCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else
            {
                updateProgress("Beginning to publish.");

                // execute next action
                Action nextAction = this.pendingActions.Dequeue();
                nextAction();
            }
        }

        /// <summary>
        /// Publishes metadata asynchronously.
        /// </summary>
        private void publishMetadata()
        {
            updateProgress("Publishing metadata.");

            this.client.PublishMetadataAsync(Microsoft.Phone.Info.DeviceStatus.DeviceName, ExplainViewModel.Instance.Term);
        }

        /// <summary>
        /// Occurs when call to publishmetadata is completed.
        /// </summary>
        /// <param name="sender">Event related object.</param>
        /// <param name="e">Event specific arguments.</param>
        private void client_PublishMetadataCompleted(object sender, PublishMetadataCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else
            {
                this.presentationId = e.Result;

                // execute next action
                Action nextAction = this.pendingActions.Dequeue();
                nextAction();
            }
        }

        /// <summary>
        /// Uploads content asynchronously.
        /// </summary>
        /// <param name="index">Index of slide.</param>
        /// <param name="isImage">True if content is image else false.</param>
        private void uploadContent(int index, bool isImage)
        {
            ContentInfo info;
            byte[] buffer;
            ContentTypes contentType;

            info = new ContentInfo();
            info.SlideIndex = index;
            info.IsImage = isImage;

            if (isImage)
            {
                buffer = ExplainViewModel.Instance.ReadFile(((LocalSlide)this.viewModel.Slides[index]).LocalImageSource);
                contentType = ContentTypes.JPEG;
            }
            else
            {
                buffer = ExplainViewModel.Instance.ReadFile(((LocalSlide)this.viewModel.Slides[index]).LocalMediaSource);

                if (this.viewModel.Slides[index].MediaType == MediaTypes.Video)
                {
                    contentType = ContentTypes.MP4;
                }
                else
                {
                    contentType = ContentTypes.WAV;
                }
            }

            updateProgress("Uploading content of slide " + (index + 1).ToString());

            this.client.UploadContentAsync(this.presentationId, contentType, buffer, info);
        }

        /// <summary>
        /// Occurs when content upload is completed.
        /// </summary>
        /// <param name="sender">Event related object.</param>
        /// <param name="e">Event specific arguments.</param>
        private void client_UploadContentCompleted(object sender, UploadContentCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else
            {
                ContentInfo info = (ContentInfo)e.UserState;
                Uri contentUri = e.Result;

                if (info.IsImage)
                {
                    this.viewModel.Slides[info.SlideIndex].ImageSource = contentUri;
                }
                else
                {
                    this.viewModel.Slides[info.SlideIndex].MediaSource = contentUri;
                }

                Action nextAction = this.pendingActions.Dequeue();
                nextAction();
            }
        }

        /// <summary>
        /// Publishes content asynchronously.
        /// </summary>
        private void publishContent()
        {
            this.updateProgress("Finalizing publishing of content.");

            Slide[] slides = new Slide[3];
            for (int i = 0; i < this.viewModel.Slides.Length; i++)
            {
                slides[i] = new Slide();
                slides[i].Question = this.viewModel.Slides[i].Question;
                slides[i].Answer = this.viewModel.Slides[i].Answer;
                slides[i].MediaType = this.viewModel.Slides[i].MediaType;
                slides[i].ImageSource = this.viewModel.Slides[i].ImageSource;
                slides[i].MediaSource = this.viewModel.Slides[i].MediaSource;
                slides[i].MediaDuration = this.viewModel.Slides[i].MediaDuration;
            }

            this.client.PublishContentAsync(this.presentationId, slides);
        }

        /// <summary>
        /// Occurs when publishcontent call is completed.
        /// </summary>
        /// <param name="sender">Event related object.</param>
        /// <param name="e">Event specific arguments.</param>
        private void client_PublishContentCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            else
            {
                MessageBox.Show("Congratulations! Your presentation has been succesfully published.");
                NavigationService.Navigate(new Uri("/MainSearchPage.xaml", UriKind.Relative));
            }
        }

        /// <summary>
        /// Adds all actions to be executed to a queue, so they can
        /// be executed asynchronously.
        /// </summary>
        private void publishPresentation()
        {
            this.pendingActions.Enqueue(this.publishMetadata);

            Slide[] slides = this.viewModel.Slides;
            for (int i = 0; i < slides.Length; i++)
            {
                // due to lambdas variable scoping, need to store
                // current value of variable, so exactly this local value is passed
                // to delegated method, not the global one, i.e. last value of i
                var index = i;

                if (!string.IsNullOrEmpty(slides[i].Question))
                {
                    if (((LocalSlide)slides[i]).LocalImageSource != null)
                    {
                        this.pendingActions.Enqueue(() =>
                        {
                            this.uploadContent(index, true);
                        });
                    }

                    if (((LocalSlide)slides[i]).LocalMediaSource != null)
                    {
                        this.pendingActions.Enqueue(() =>
                        {
                            this.uploadContent(index, false);
                        });
                    }
                }
            }

            this.pendingActions.Enqueue(this.publishContent);
        }

        /// <summary>
        /// Occurs when user clicks on publish presentation button.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnPublish_Click(object sender, RoutedEventArgs e)
        {
            btnPublish.IsEnabled = false;

            if (this.client != null)
            {
                unhookFromWebService();
                hookToWebService();
            }

            this.pendingActions.Clear();
            this.publishPresentation();
            this.totalPendingActions = this.pendingActions.Count;

            this.client.OpenAsync();
        }

        /// <summary>
        /// Updates the UI elements with progress of publishing.
        /// </summary>
        /// <param name="message">Message to be displayed.</param>
        private void updateProgress(string message)
        {
            int totalActionsDone = this.totalPendingActions - this.pendingActions.Count;
            double percentDone = (((double)totalActionsDone / (double)this.totalPendingActions) * 100.0);

            this.txtblockProgress.Text = message
                + Environment.NewLine
                + Environment.NewLine
                + ((int)percentDone).ToString()
                + "% completed.";
        }

        /// <summary>
        /// Hooks to webservice events.
        /// </summary>
        private void hookToWebService()
        {
            this.client = new StemServiceClient();
            this.client.OpenCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_OpenCompleted);
            this.client.PublishMetadataCompleted += new EventHandler<PublishMetadataCompletedEventArgs>(client_PublishMetadataCompleted);
            this.client.UploadContentCompleted += new EventHandler<UploadContentCompletedEventArgs>(client_UploadContentCompleted);
            this.client.PublishContentCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_PublishContentCompleted);
        }

        /// <summary>
        /// Unhooks from webservice events.
        /// </summary>
        private void unhookFromWebService()
        {
            this.client.OpenCompleted -= new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_OpenCompleted);
            this.client.PublishMetadataCompleted -= new EventHandler<PublishMetadataCompletedEventArgs>(client_PublishMetadataCompleted);
            this.client.UploadContentCompleted -= new EventHandler<UploadContentCompletedEventArgs>(client_UploadContentCompleted);
            this.client.PublishContentCompleted -= new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_PublishContentCompleted);
            this.client.CloseAsync();
        }

        #endregion

        #region Public members

        /// <summary>
        /// Initializes a new instance of PublishResults.
        /// </summary>
        public PublishResults()
        {
            InitializeComponent();

            this.pendingActions = new Queue<Action>();
            this.presentationId = uint.MinValue;
            this.totalPendingActions = 0;
            this.viewModel = ExplainViewModel.Instance;
        }

        #endregion

        #region Protected members

        /// <summary>
        /// Occurs when page is navigated to, from some other page.
        /// </summary>
        /// <param name="e">Navigation event arguments.</param>
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            hookToWebService();
        }

        /// <summary>
        /// Occurs when page is navigated from, to other page.
        /// </summary>
        /// <param name="e">Event specific arguments.</param>
        protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);

            unhookFromWebService();

            btnPublish.IsEnabled = true;
        }

        #endregion
    }
}